
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Text;

using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.Design;

using Swaf.Container;

namespace Swaf.Gui.AspNet2
{
	[ToolboxData("<{0}:UserControl runat=server></{0}:UserControl>")]
	/// <summary>
	/// Acts similar to the standard asp.net UserControl, but allows it to be placed
	/// in a class library dll which makes it much easy to reuse UserControls accross
	/// different asp.net applications.
	/// </summary>
	/// <remarks>
	/// Many of the less used ASP.NET embedded elements within the html are not supported, but
	/// it does allow asp controls and other user controls to be placed in the page.  It also supports 
	/// the following:
	/// 
	/// <%=methodName(paramValues)%> to embed the return value of the method call into the html
	/// stream where the server block was embedded.  methodName needs to be a public method
	/// of the class that takes the same matching values as being passed.  The params need to
	/// be literal values in this version.
	/// 
	/// <%HtmlBlock(processingMethod);%>.  This is a powerful mechanism that allows the contents
	/// within it ending in an <%EndHtmlBlock;%> to be placed into the html stream 0 to N number of
	/// times depending on the behavior of the processingMethod.  The processing method 
	/// needs to take a single parameter of type HtmlBlockInfo.  HtmlBlockInfo allows the contents
	/// of the HtmlBlock to be duplicated.  Typically the processing method will iterate
	/// through a list of data such as an IBizObjList and for each object in the list
	/// call the HtmlBlockInfo.generateBlock method to duplicate the html in the HtmlBlock
	/// for the item in the list.  HtmlBlock elements can have embedded HtmlBlock elements
	/// which is good for displaying trees of data.
	/// 
	/// This class works properly when being used in VS.NET in design mode.
	/// 
	/// This class uses its Html properly to get a StreamReader for the basic html to be
	/// used for the UserControl output.  The property implementation in this class
	/// assumes the HTML can be found as an embedded resource within the dll with the same name
	/// of the final class that inherits from this class.  So, if a class called
	/// AddressDisplay inherits from this UserControl, this class will look for an embedded
	/// resource called AddressDisplay.ascx
	/// </remarks>
	public class UserControl : WebControl, INamingContainer, IWidget
	{
        private static bool s_defautlLocalize = false;

		protected enum ControlState { initialized, created, rendered };

		protected static IDictionary s_content = new ThreadSafeHashtable();
		public string m_contentName = null;
		private IList m_htmlBlocks = null;
		//private HtmlControlHelper m_helper;
		private IDictionary m_blockInfo = null;
		protected IDesignerHost m_designHost = null;
		protected ControlState m_currentState = ControlState.initialized;
		protected bool m_delayCreation = false;
        protected IDictionary m_origionalControls = new Hashtable();
        protected bool m_localized = DefaultLocalize;

		protected BizProcWidgetType m_baseCtlType = BizProcWidgetType.other;

		/// <summary>
		/// Keeps a list of the parent bizproc ids that this widget is attached to.
		/// </summary>
		protected string m_parentBizProcIds = null;
		/// <summary>
		/// Controls the ability to save important properties of the user control into the widget's offline state.
		/// This is only needed when using Ajax because during an ajax event there is no page that has the server html
		/// with the property values embedded in it.  By storing these important property values in here, they are
		/// available to the user control when it is created in an ajax environment.
		/// </summary>
		protected bool m_useOfflineState = true;
		/// <summary>
		/// Holds the name of the bizrule that will return the actual template contents, not just the name of a 
		/// embedded resource in an assembly.  If this value is non-null, the Html property will use it as a rule
		/// name to get the template.  If this value is null this class will use the template name to load an
		/// embedded resource.
		/// </summary>
		protected string m_templateBizRule = null;
		/// <summary>
		/// Holds the name of the bizrule that will return the name of the template as an assembly embedded resource.
		/// </summary>
		protected string m_templateNameBizRule = null;

        public static bool DefaultLocalize { get { return s_defautlLocalize; } set { s_defautlLocalize = value; } }

		public bool DelayControlCreation { get { return m_delayCreation; } set { m_delayCreation = value; } }

		public string TemplateBizrule { get { return (string)getOffline("m_templateBizRule"); } set { putOffline("m_templateBizRule", value); } }
		/// <summary>
		/// Indicates if the control is being used in design mode.  This happens when the
		/// control is being used in a development environment like VS.NET.
		/// </summary>
		/// <returns>True if the class is beings used in a ASP.NET page editor, false
		/// otherwise.</returns>
		public bool isInDesignMode() { return DesignMode; }

		/// <summary>
		/// If being used in design mode, the DesignHost is what allows child controls to
		/// be created by parsing a chuck of text such as "<asp:TextBox .../>"
		/// </summary>
		/// <remarks>This is used by the HtmlControlHelper class which needs this
		/// to properly parse server controls from the HTML during desing mode.  The
		/// Page.ParseControl method doesnt work properly in design mode and
		/// the IDesignerHost must be used in order to parse in design mode.</remarks>
		public IDesignerHost DesignHost 
		{ 
			get 
			{
				if(DesignMode == true  && m_designHost == null)
					m_designHost = (IDesignerHost)Site.GetService(typeof(IDesignerHost));
				return m_designHost; 
			} 
		}

        /// <summary>
        /// Marks this user control as having localized text. If this value is true for a given
        /// control, the HtmlControlHelper classes will attempt to localize property and method return
        /// values before rending them on the display.</summary>
        [DefaultValue(false), Category("Localization")]
        public bool Localized
        {
            get { return m_localized; }
            set { m_localized = value; }
        }

		public UserControl() { }
		public UserControl(ActiveWidget widget)
		{
			ID = widget.State.Id;
		}

		protected override HtmlTextWriterTag TagKey
		{
			get
			{
				return HtmlTextWriterTag.Div;
			}
		}

		public override string CssClass
		{
			get
			{
				return base.CssClass;
			}
			set
			{
				base.CssClass = value;
				if(State != null && State.State != null)
					State.State.put("CssClass", value);
			}
		}
		/// <summary>
		/// During Onload the controls for the UserControl need to be created in order
		/// for them to properly handle events that may have been trigger from them as
		/// well as for them to manage their state with the ViewState.
		/// </summary>
		protected override void  OnLoad(EventArgs e)
		{
		 	base.OnLoad(e);
			createControls();
			if (!Cmn.IsEmpty(State.State.get("CssClass")))
				CssClass = (string)State.State.get("CssClass");
		}

		protected override void Render(HtmlTextWriter writer)
		{
			base.Render(writer);
			State.State.put("CssClass", CssClass);
		}

		protected virtual bool ShouldCacheStructure { get { return true; } }

		internal IPageContent getPageAndRenderIfNeeded()
		{
			HtmlBlockContent c = null;
			string key = null;
			if (ShouldCacheStructure)
			{
				key = createCacheKey();
				c = (HtmlBlockContent)s_content[key];
			}
			if (c == null)
				lock (this.GetType())
				{
                    if (!ShouldCacheStructure || (c = (HtmlBlockContent)s_content[key]) == null)
                    {
						TextReader htmlReader = Html;
						if (htmlReader != null)
						{
							c = new HtmlBlockContent();
							if(ShouldCacheStructure)
								s_content[key] = c;
							HtmlControlHelper h = new HtmlControlHelper(Page, this);
							h.processContent(c, htmlReader, null);
						}
                    }
				}
			return c;
		}

        public IDictionary OrigionalControls { get { return m_origionalControls; } }

		protected virtual bool createControls()
		{
			try
			{
				if (m_currentState == ControlState.initialized)
				{
					IPageContent c = getPageAndRenderIfNeeded();
					m_currentState = ControlState.created;
					if(c != null)
						c.createControls(Swaf.Application.currentApp, this, null, null, null);
                    return true;
				}
			}
			catch
			{
			}
            return false;
		}

        protected void clearControls()
        {
            if (m_currentState == ControlState.created)
            {
                m_currentState = ControlState.initialized;
				m_contentName = null;
                Controls.Clear();
                m_blockInfo = null;
            }
        }

		protected internal virtual void processChildSkinId(Control child)
		{
		}

		/// <summary>
		/// Gets the HTML for the UserControl.  This is the main content displayed for
		/// the user control when embedded in a Page.
		/// </summary>
		/// <remarks>
		/// The implementation here searches for a file embedded within the resources of the
		/// assembly from the final class this class inherits from.  If a class called
		/// AddressDisplay inherits from this class and is in an assembly called MyDisplays.dll
		/// It will look in MyDisplays.dll for the resource stream.  This class uses the
		/// ContentName property to get the name of the stream.
		/// 
		/// This method can be overriden by a child class in order to change the way in which the
		/// HTML is retrieved for the user control.  If multiple embedded html files can
		/// be used by one control, it is better to override the ContentName property and
		/// dynamically change the name of the resource in the assembly then to override this
		/// method.  Overriding this method is best reserved for changing the place the resource 
		/// is found or to process the contents of the stream before it is used (such as decrypting
		/// the html before it is processed).
		/// </remarks>
		protected virtual TextReader Html
		{
			get
			{
				if (m_templateBizRule != null)
					return getHtmlFromBizRule();
				else
					return getHtmlFromAssemblyResource();
			}
		}

		protected virtual TextReader getHtmlFromBizRule()
		{
			IResults rc = Swaf.Application.currentApp.ruleMgr.execute(m_templateBizRule);
			if (rc.ExceptionInfo != null || rc.CallResults == null)
				throw new BaseException(string.Format("failure to call bizrule named '{0}'", m_templateBizRule), rc.ExceptionInfo);
			return new StringReader(rc.CallResults.ToString());
		}

		protected virtual TextReader getHtmlFromAssemblyResource()
		{
			StringBuilder buff = new StringBuilder(1024);
			string templateName = ContentName;
			string[] templates = templateName==null?new string[0]:templateName.Split(',');
			foreach (string template in templates)
			{
				string tName = template.Trim();
				if (tName.Length != 0)
				{
					Assembly ass = null;
					int index = tName.LastIndexOf(".");
					if (index != -1)
						ass = Assembly.LoadWithPartialName(tName.Substring(0, index));
					if (ass != null)
					{
						Stream s = ass.GetManifestResourceStream(tName + ".ascx");
						if (s != null)
						{
							TextReader r = new StreamReader(s);
							buff.Append(r.ReadToEnd());
						}
					}
				}
			}
			return buff.Length==0?null:new StringReader(buff.ToString());
		}

		/// <summary>UserManagement.Gui.AspNet.MembershipLimitsRODisplay,Articles.Gui.AspNet.LimitsRWDisplay,CurrencyExchange.Gui.AspNet.CompanyCurrencyLimitsDisplay,Reservations.Gui.AspNet.LimitsRWDisplay"	string

		/// This property provides the name of the resource for the HTML embedded in an assembly
		/// </summary>
		/// <remarks>
		/// The implementation in this class provides the name of the resource by using the namespace,
		/// class name, and ".ascx".  For example if the final class inheriting from this class is
		/// MyDisplays.AddressDisplay in the MyDisplays.dll assembly, then the name returned by this
		/// property will be MyDisplays.AddressDisplay.ascx.
		/// </remarks>
		public virtual string ContentName
		{
			get
			{
				if (Cmn.IsEmpty(m_contentName))
					if(TemplateNameBizRule != null)
						m_contentName = getTemplateNames();
					else
						m_contentName = this.GetType().Namespace + "." + this.GetType().Name;
				return m_contentName;
			}
			set
			{
				m_contentName = value;
			}
		}

		protected internal IList HtmlBlocks
		{
			get
			{
				if (m_htmlBlocks == null)
					m_htmlBlocks = new ArrayList();
				return m_htmlBlocks;
			}
		}

		protected internal IDictionary BlockInfo
		{
			get
			{
				if (m_blockInfo == null)
					m_blockInfo = new Hashtable();
				return m_blockInfo;
			}
		}

		/// <summary>
		/// Once the events and other processing of the page occurs, the RenderContents
		/// is called by the Page.  This method will render the contents of the html and
		/// all the controls and other server side components contained within the
		/// instance of the UserControl.
		/// </summary>
		/// <remarks>
		/// When this class is called during normal ASP.NET usage, the creation of the
		/// controls will happen during the OnLoad method.  However, in design mode the
		/// OnLoad method is not called at all, but this method is.  When this happens
		/// this method is prepared to first create the controls on embedded within
		/// this instance before actually rendering them as well.
		/// </remarks>
		/// <param name="output"></param>
		protected override void RenderContents(HtmlTextWriter output)
		{
			string key = createCacheKey();
			//if (DesignMode == true)
			if (m_currentState == ControlState.initialized)
				createControls();
			IPageContent c = (IPageContent)s_content[key];
			if (c == null || c.ChildCount == 0)
				base.RenderContents(output);
			else
			{
				c.renderContent(Swaf.Application.currentApp, this, output, null, null);
				m_currentState = ControlState.rendered;
			}
		}

		protected virtual string createCacheKey()
		{
			return this.GetType().FullName + ContentName;
		}
    
        protected object getViewState(string fieldName, object defaultValue)
        {
            object val = ViewState[fieldName];
            return val == null ? defaultValue : val;
        }

		protected string getTemplateNames()
		{
			string templateName = null;
			if (TemplateNameBizRule != null)
			{
				object[] tempRuleCallParams = getParameterForTemplateBizRuleCall();
				IResults rc = null;
				if(tempRuleCallParams == null)
					rc = Swaf.Application.currentApp.ruleMgr.execute(TemplateNameBizRule);
				else
					rc = Swaf.Application.currentApp.ruleMgr.execute(TemplateNameBizRule, tempRuleCallParams);
				if (rc.CallResults != null)
					templateName = rc.CallResults.ToString();
				if (!Cmn.IsEmpty(templateName))
					ViewState["TemplateName"] = templateName;
				else
					templateName = ViewState["TemplateName"] as string;
			}
			return templateName;
		}

		protected virtual object[] getParameterForTemplateBizRuleCall()
		{
			return null;
		}

		public string TemplateNameBizRule { get { return (string)getOffline("m_templateNameBizRule"); } set { putOffline("m_templateNameBizRule", value); } }

		public virtual string WidgetId
		{
			get { return ClientID; }
		}

		public string ParentBizProcIds
		{
			set { putOffline("m_parentBizProcIds", value); }
			get { return (string)getOffline("m_parentBizProcIds"); }
		}

		public virtual BizProcWidgetType BizProcWidgetType { get { return m_baseCtlType; } set { m_baseCtlType = value; } }

		protected BizProcCoordinator BPC
		{
			get { return (Page is IPage) ? ((IPage)Page).BPC : null; }
		}

		protected WidgetOfflineState State
		{
			get
			{
				WidgetOfflineState state = BPC==null?null:BPC.getWidgetOfflineState(WidgetId);
				if (state == null && BPC != null)
				{
					BPC.addWidgetOfflineState(state = new WidgetOfflineState(WidgetId, this.GetType().AssemblyQualifiedName));
					state.setParentBizProcIds(m_parentBizProcIds);
					state.BizProcWidgetType = this.BizProcWidgetType;
				}
				return state;
			}
		}

		protected void putOffline(string keyName, object val)
		{
			if (m_useOfflineState && BPC != null)
				State.State.put(keyName, val);
			Cmn.callPropertySet(this, keyName, val);
		}
		protected object getOffline(string keyName)
		{
			object v = BPC==null?null:State.State.get(keyName);
			if (v == null)
				v = Cmn.callPropertyGet(this, keyName);
			return v;
		}
	
		protected Control searchControl(string id, Control parent)
		{
			foreach (Control c in parent.Controls)
				if (id.Equals(c.ID))
					return c;
				else
				{
					Control theOne = searchControl(id, c);
					if (theOne != null)
						return theOne;
				}
			return null;
		}
	}
}
